package fybug.nulll.pdstream.IO.Stream;
import com.sun.istack.internal.NotNull;
import com.sun.istack.internal.Nullable;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;

import fybug.nulll.pdfunctionlibrary.Processing.Err.Stop;
import fybug.nulll.pdfunctionlibrary.Util.Processing.Arrarys;
import fybug.nulll.pdstream.IO.Error.ByteStreamErr;
import fybug.nulll.pdstream.IO.I_O;
import fybug.nulll.pdstream.Interface.UpdateSize;
import fybug.nulll.pdstream.Source.StreamSource;
import fybug.nulll.pdstream.StreamOperator;
/**
 * <h2>字节流读取器.</h2>
 * <pre>
 * <b>建议使用工场类 {@link I_O} 构造</b>
 *
 * 从绑定的 <b>操作源</b> 中获取的被操作流进行读取
 * 支持 <b>批量读取</b>
 * 可使用该操作器中的方法对操作源进行 <b>流的重绑定</b>
 * 可以使用 <b>连续操作</b> ,有效解决不稳定流的处理问题
 *
 * 但不建议使用该类中的方法 <b>读取大量数据</b>
 * 会 <b>内存不足</b> 的</pre>
 * <pre>常规操作方法：
 * {@link #input()}
 * </pre>
 *
 * @author fybug
 * @version 0.0.8
 * @see StreamOperator
 * @see InputStream
 * @see I_O
 * @see ByteStreamErr
 * @since JDK 1.8
 */
@SuppressWarnings( "all" )
public final
class Input extends StreamOperator<InputStream, Object> {
    /** <p>构造一个读取器并绑定字节源.</p> */
    public
    Input(@Nullable final StreamSource<InputStream, Object> r) { binSource(r); }

    /*
     * BinSource
     */

    @Override
    @NotNull
    public final
    Input binSource(@Nullable final StreamSource<InputStream, Object> r)
    { return (Input) super.binSource(r); }

    /** <p>重绑定操作源中的流.</p> */
    @NotNull
    public final
    Input binInputStream(@Nullable final InputStream... r) {
        try {
            source().binStream(r);
        } catch ( Stop ignored ) {
        }
        return this;
    }

    /** <p>增加操作源中的流.</p> */
    @NotNull
    public final
    Input appendInputStream(@NotNull final InputStream... r) {
        try {
            @NotNull StreamSource<InputStream, Object> streamByteSource = source();
            @Nullable InputStream[] inputStreams;
            try {
                inputStreams = streamByteSource.stream();
            } catch ( Stop e ) {
                inputStreams = null;
            }
            streamByteSource.binStream(Arrarys.append(inputStreams, r));
        } catch ( Stop ignored ) {
        }
        return this;
    }

    /*
     * Read
     */

    /**
     * <p>读取绑定的流的字节数据.</p>
     * <pre>
     * 使用{@code byte[]} <b>按顺序</b> 将流中的数据读出
     * 适用于稳定的流
     * </pre>
     *
     * @throws ByteStreamErr 读取过程发生错误
     */
    @NotNull
    public final
    byte[] input() throws ByteStreamErr {
        @NotNull final InputStream[] r;
        // 检查
        try {
            r = source().stream(); // 读取流组
        } catch ( Stop e ) {
            return new byte[0];
        }
        int size = 0, // 总长度记录
                streamSize[] = new int[r.length]; // 流长度记录
        @NotNull byte[] bs; // 读取缓存
        @NotNull InputStream inputStream = null;
        try {
            synchronized ( r ){
                for ( int i = 0, nowSize; i < r.length; i++ ){
                    inputStream = r[i]; // 减少寻址
                    // 获取流长度
                    nowSize = inputStream.available();
                    streamSize[i] = nowSize;
                    size += nowSize;
                }
                bs = new byte[size];
                // 重初始化为位置指针
                size = 0;
                for ( int i = 0, nowSize; i < r.length; i++ ){
                    inputStream = r[i];
                    nowSize = streamSize[i];
                    // 读取到数组中的指定位置
                    inputStream.read(bs, size, nowSize);
                    size += nowSize;
                }
            }
        } catch ( IOException e ) {
            throw new ByteStreamErr(e.getMessage(), inputStream, null);
        }
        return bs;
    }

    /**
     * <p>使用连续操作从绑定的流中进行读取.</p>
     * <pre>
     * 将绑定的流 <b>按顺序</b> 进行读取
     * 每次读取一个片段
     * 适用于所有流
     * </pre>
     *
     * @param size 片段大小
     *
     * @throws ByteStreamErr 读取出错
     */
    @NotNull
    public final
    byte[] sustained(final int size) throws ByteStreamErr
    { return monitor(size, checkUpSize()); }

    /**
     * <p>使用连续操作从绑定的流中进行读取.</p>
     * <pre>
     * 将绑定的流 <b>按顺序</b> 进行读取
     * 每次读取一个片段
     * 可以使用接口 <b>动态调整读取片段</b> 大小
     * 在读取过程中设置片段大小为{@code 0} 可以中断读取
     * 适用于所有流
     * </pre>
     *
     * @param size       初始片段大小
     * @param updateSize 监听接口
     *
     * @throws ByteStreamErr 读取出错
     * @see UpdateSize
     */
    @NotNull
    public final
    byte[] monitor(final int size, @Nullable final UpdateSize updateSize) throws ByteStreamErr
    {
        @NotNull InputStream[] inputStream;
        try {
            inputStream = source().stream();
            // 参数错误
            if (size <= 0)
                return new byte[0];
        } catch ( Stop e ) {
            return new byte[0];
        }
        @NotNull //* 使用字节缓存流做暂存区 *//
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); // 数据暂存区
        @NotNull byte[] bytes = new byte[size]; // 读取缓冲区
        @Nullable InputStream in = null;
        int readSize; // 读取出来的数据量
        try {
            synchronized ( inputStream ){
                if (updateSize != null)
                    updateSize.init();
                for ( int i = 0; i < inputStream.length; i++ ){
                    /* 读取所有流 */
                    in = inputStream[i]; // 减少寻址
                    for ( readSize = in.read(bytes); // 读取数据到缓冲区
                            readSize != -1; // 是否成功读取数据
                            readSize = in.read(bytes) ){
                        // 根据读取的数据量写入缓存流
                        outputStream.write(bytes, 0, readSize);
                        if (readSize < bytes.length)
                            /* 读取完成 */
                            break;
                        bytes = Buff.upBufferSize(updateSize, bytes, readSize);
                    }
                }
            }
            bytes = null;
            in = null;
            readSize = 0;
        } catch ( Stop ignored ) {
        } catch ( IOException e ) {
            throw new ByteStreamErr(e.getMessage(), in, null);
        } finally {
            if (updateSize != null)
                updateSize.destroy();
        }
        return outputStream.toByteArray();
    }
}
